﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace RoketPack.IM
{
    internal class MessageQueue
    {
        private Dictionary<string, ChatWindow> m_Windows = new Dictionary<string, ChatWindow>();
        private List<Message> m_Messages = new List<Message>();
        private Thread m_Thread = null;
        private jabber.client.JabberClient m_Client = null;
        private object m_Lock = new object();
        private bool m_Running = true;

        /// <summary>
        /// Creates a new IM message queue and begins the processing
        /// thread.
        /// </summary>
        /// <param name="client">The jabber client to recieve messages from and send message to.</param>
        internal MessageQueue(jabber.client.JabberClient client)
        {
            this.m_Client = client;

            this.m_Thread = new Thread(this.Process);
            this.m_Thread.Name = "IM Message Queue Processor";
            this.m_Thread.IsBackground = true;
            this.m_Thread.Start();
        }

        /// <summary>
        /// The destructor for a MessageQueue object, which cleans up the
        /// currently running thread.
        /// </summary>
        ~MessageQueue()
        {
            if (this.m_Thread.IsAlive)
                this.m_Thread.Abort();
        }

        /// <summary>
        /// Closes the message queue.
        /// </summary>
        internal void Close()
        {
            this.m_Running = false;
            while (this.m_Thread.IsAlive) ;

            List<string> k = new List<string>();
            foreach (KeyValuePair<string, ChatWindow> kv in this.m_Windows)
                k.Add(kv.Key);
            foreach (string s in k)
            {
                if (this.m_Windows[s] != null)
                {
                    try
                    {
                        RoketPack.Manager.VoidLambda l = () =>
                            {
                                this.m_Windows[s].Close();
                            };
                        this.m_Windows[s].Invoke(l);
                    }
                    catch (ObjectDisposedException)
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Initiates an empty chat window with the specified contact.  Used
        /// when the user double clicks a contact in the FriendUI control.
        /// </summary>
        /// <param name="contact">The contact to initiate chat with.</param>
        internal void Initiate(Contact contact)
        {
            lock (this.m_Lock)
            {
                this.m_Messages.Add(new Message(contact));
            }
        }

        /// <summary>
        /// Add a recieved message to the message queue, to be sent to the
        /// correct chat window.
        /// </summary>
        /// <param name="from">The contact that the message was sent from.</param>
        /// <param name="msg">The message text.</param>
        internal void Recieve(Contact from, string msg)
        {
            if (msg == null)
                return;

            lock (this.m_Lock)
            {
                this.m_Messages.Add(new Message(from, null, msg));
            }
        }

        /// <summary>
        /// Adds a sent message to the message queue, to be shown on the
        /// correct chat window.  This function also actually sends the message
        /// to the specified contact through the JabberClient.
        /// </summary>
        /// <param name="to">The contact that the message should be sent to.</param>
        /// <param name="msg">The message text.</param>
        internal void Send(Contact to, string msg)
        {
            if (msg == null)
                return;

            // Tell the client to send the message.
            jabber.protocol.client.Message reply = new jabber.protocol.client.Message(this.m_Client.Document);
            reply.Body = msg;
            if (reply.Body != "")
            {
                reply.To = to.ID;
                this.m_Client.Write(reply);

                lock (this.m_Lock)
                {
                    this.m_Messages.Add(new Message(null, to, msg));
                }
            }
        }

        /// <summary>
        /// Adds a notification to the message queue, to be sent to the specified
        /// contact.  This will result in a notification appearing on the contact's
        /// screen.
        /// </summary>
        /// <param name="to">The contact that the notification should be sent to.</param>
        /// <param name="msg">The notification text.</param>
        internal void SendNotification(Contact to, string msg)
        {
            if (msg == null)
                return;

            // Tell the client to send the message.
            CustomPacket notification = new CustomPacket(this.m_Client.Document);
            notification.Message = msg;
            notification.Type = "notification";
            if (notification.Message != "")
            {
                notification.To = to.ID;
                this.m_Client.Write(notification);
            }
        }

        /// <summary>
        /// Processes the IM message queue, managing the chat windows and messages.
        /// </summary>
        private void Process()
        {
            try
            {
                while (this.m_Running)
                {
                    List<Message> messages = null;
                    lock (this.m_Lock)
                    {
                        messages = new List<Message>(this.m_Messages);
                        this.m_Messages.Clear();
                    }

                    // Process all the messages
                    foreach (Message m in messages)
                    {
                        Contact c = m.Contact;

                        if (!this.m_Windows.Keys.Contains(c.ID) || this.m_Windows[c.ID] == null)
                        {
                            object pulser = new object();
                            bool complete = false;
                            Thread t = new Thread(() =>
                                {
                                    try
                                    {
                                        ChatWindow w = new ChatWindow(this, c, new Contact(this.m_Client.JID, null));
                                        w.Load += (sender, e) =>
                                            {
                                                if (m.IsTo)
                                                    w.AppendSentMessage(m.To, m.Data);
                                                else if (m.IsFrom)
                                                    w.AppendRecievedMessage(m.From, m.Data);

                                                w.UpdateStatus(c);
                                            };
                                        w.FormClosed += (sender, e) =>
                                            {
                                                this.m_Windows[c.ID] = null;
                                            };
                                        c.StatusUpdated += (sender, e) =>
                                            {
                                                RoketPack.Manager.VoidLambda lambda = () =>
                                                {
                                                    w.UpdateStatus(c);
                                                };

                                                if (w.InvokeRequired)
                                                    w.Invoke(lambda);
                                                else
                                                    lambda();
                                            };
                                        if (!this.m_Windows.Keys.Contains(c.ID))
                                            this.m_Windows.Add(c.ID, w);
                                        else
                                            this.m_Windows[c.ID] = w;
                                        lock (pulser)
                                        {
                                            complete = true;
                                            Monitor.Pulse(pulser);
                                        }
                                        w.ShowDialog();
                                    }
                                    catch (Exception ex)
                                    {
                                        MessageBox.Show(ex.ToString());
                                        complete = true;
                                    }
                                });
                            t.Name = "IM Chat Window - " + c.ID;
                            t.IsBackground = true;
                            t.Start();

                            // We have to wait until the form has been added to the dictionary.
                            lock (pulser)
                            {
                                while (!complete)
                                    Monitor.Wait(pulser);
                            }
                        }
                        else
                        {
                            RoketPack.Manager.VoidLambda lambda = () =>
                                {
                                    if (m.IsTo)
                                        this.m_Windows[c.ID].AppendSentMessage(m.To, m.Data);
                                    else if (m.IsFrom)
                                        this.m_Windows[c.ID].AppendRecievedMessage(m.From, m.Data);
                                };

                            if (this.m_Windows[c.ID].InvokeRequired)
                                this.m_Windows[c.ID].Invoke(lambda);
                            else
                                lambda();
                        }
                    }

                    // Sleep for 10 milliseconds.
                    Thread.Sleep(10);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                MessageBox.Show("MessageQueue process has terminated!");
            }
        }
    }

    /// <summary>
    /// A class which represents a message sent to or received from a
    /// contact.
    /// </summary>
    internal class Message
    {
        Contact p_From = null;
        Contact p_To = null;
        string p_Message = null;
        bool p_InitiateOnly = false;

        /// <summary>
        /// Creates a new Message object to represent a message sent to
        /// or recieved from a contact.  Either one the from or to
        /// arguments should be null to indicate whether this message was
        /// sent or recieved (but not both).
        /// </summary>
        /// <param name="from">The contact this message was recieved from, or null if the message was sent.</param>
        /// <param name="to">The contact this message was sent to, or null if the message was recieved.</param>
        /// <param name="message">The message data.</param>
        internal Message(Contact from, Contact to, string message)
        {
            this.p_From = from;
            this.p_To = to;
            this.p_Message = message;
        }

        /// <summary>
        /// Creates a new Message object that instructs the MessageQueue to
        /// just show a new, empty chat window without sending or recieving
        /// any messages.
        /// </summary>
        /// <param name="to">The contact that the chat should be initiated with.</param>
        internal Message(Contact to)
        {
            this.p_To = to;
            this.p_InitiateOnly = true;
        }

        /// <summary>
        /// The contact this message was recieved from, or null if the message was sent.
        /// </summary>
        internal Contact From
        {
            get
            {
                return this.p_From;
            }
        }

        /// <summary>
        /// The contact this message was sent to, or null if the message was recieved.
        /// </summary>
        internal Contact To
        {
            get
            {
                return this.p_To;
            }
        }

        /// <summary>
        /// The contact that this message was recieved from or sent to.
        /// </summary>
        internal Contact Contact
        {
            get
            {
                if (this.From != null)
                    return this.p_From;
                else
                    return this.p_To;
            }
        }

        /// <summary>
        /// Whether the message was recieved from a contact.
        /// </summary>
        internal bool IsFrom
        {
            get
            {
                return (this.p_From != null && !this.p_InitiateOnly);
            }
        }

        /// <summary>
        /// Whether the message was sent to a contact.
        /// </summary>
        internal bool IsTo
        {
            get
            {
                return (this.p_To != null && !this.p_InitiateOnly);
            }
        }

        /// <summary>
        /// Whether the message only initates a new chat window.
        /// </summary>
        internal bool IsInitiateOnly
        {
            get
            {
                return (this.p_InitiateOnly);
            }
        }

        /// <summary>
        /// The message data.
        /// </summary>
        internal string Data
        {
            get
            {
                return this.p_Message;
            }
        }
    }
}
